﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;

namespace PhiStore
{
    /// <summary>
    /// Singleton class for storing and receiving settings
    /// </summary>
    public class SettingsManager
    {
        private static SettingsManager instance = null;
        private string SettingsPath = Application.UserAppDataPath + Path.DirectorySeparatorChar;

        private BinaryFormatter formatter;
        private Dictionary<string, object> settings;
        private bool storeSettings = false;

        private object mutex = new object();

        private SettingsManager()
        {
            settings = new Dictionary<string, object>();
            formatter = new BinaryFormatter();
            ReadAllSettings();
        }

        /// <summary>
        /// Returns the singleton instance
        /// </summary>
        public static SettingsManager Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new SettingsManager();
                }
                return instance;
            }
        }

        /// <summary>
        /// Dictates whether or not settings should be stored
        /// </summary>
        public bool StoreSettings
        {
            set
            {
                lock (mutex)
                {
                    if (!storeSettings && value)
                    {
                        StoreAllSettings();
                    }
                    storeSettings = value;
                }

            }
            get
            {
                lock (mutex)
                {
                    return storeSettings;
                }
            }
        }

        private void DeleteSetting(string setting)
        {
            if (settings.ContainsKey(setting))
                settings.Remove(setting);
        }

        /// <summary>
        /// Adds a setting
        /// </summary>
        /// <param name="setting">the name of the setting</param>
        /// <param name="value">serializable setting to store</param>
        public void Add(string setting, object value)
        {
            lock (mutex)
            {
                DeleteSetting(setting);
                settings.Add(setting, value);

                if (storeSettings)
                    SaveSetting(setting, value);

            }
        }

        /// <summary>
        /// reads a setting
        /// </summary>
        /// <typeparam name="T">Typecast to this type is performed</typeparam>
        /// <param name="setting">The name of the setting</param>
        /// <returns>The object read</returns>
        public T Read<T>(string setting)
        {
            T result = default(T);
            lock (mutex)
            {
                if (settings.ContainsKey(setting))
                    result = (T)settings[setting];
                else
                    throw new ArgumentException(setting + " not found");
            }
            return result;
        }

        private void SaveSetting(string setting, object value)
        {
            if (value.GetType().IsSerializable)
            {
                if (File.Exists(SettingsPath + setting))
                    File.Delete(SettingsPath + setting);

                FileStream fs = null;
                try
                {
                    fs = File.OpenWrite(SettingsPath + setting);
                    formatter.Serialize(fs, value);
                    fs.Flush();
                }
                catch
                {
                    Log.Instance.AddLogEntry("Settings", "Saving setting failed: " + setting);
                }
                finally
                {
                    if (fs != null)
                        fs.Close();
                }
            }
        }

        private object LoadSetting(string setting)
        {
            object result = null;
            if (File.Exists(SettingsPath + setting))
            {
                FileStream fs = null;
                try
                {
                    fs = File.OpenRead(SettingsPath + setting);
                    result = formatter.Deserialize(fs);
                }
                catch
                {
                    Log.Instance.AddLogEntry("Settings","Loading setting failed: " + setting);
                }
                finally
                {
                    if (fs != null)
                        fs.Close();
                }

            }
            return result;
        }


        private void StoreAllSettings()
        {
            foreach (string key in settings.Keys)
            {
                SaveSetting(key, settings[key]);
            }
        }

        private void ReadAllSettings()
        {
            if (Directory.Exists(SettingsPath))
            {
                DirectoryInfo di = new DirectoryInfo(SettingsPath);

                FileInfo[] settingFiles = di.GetFiles();
                foreach (FileInfo fi in settingFiles)
                {
                    object setting = LoadSetting(fi.Name);
                    if (setting != null)
                    {
                        this.Add(fi.Name, setting);
                    }
                }
            }
        }

    }
}
